{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Non-correspondences"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/home/yoyee/Documents/deepSfm\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<style>.container { width:100% !important; }</style>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/home/yoyee/Documents/deepSfm\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import sys\n",
    "module_path = os.path.abspath(os.path.join('..'))\n",
    "if module_path not in sys.path:\n",
    "    sys.path.append(module_path)\n",
    "print(module_path)\n",
    "%matplotlib inline\n",
    "from IPython.core.display import display, HTML\n",
    "display(HTML(\"<style>.container { width:100% !important; }</style>\"))\n",
    "os.chdir('../')\n",
    "print(os.getcwd())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "# import dense_correspondence_manipulation.utils.utils as utils\n",
    "# utils.add_dense_correspondence_to_python_path()\n",
    "import utils.correspondence_tools.correspondence_plotter as correspondence_plotter\n",
    "import utils.correspondence_tools.correspondence_finder as correspondence_finder\n",
    "# from dense_correspondence.dataset.spartan_dataset_masked import SpartanDataset\n",
    "import os\n",
    "import numpy as np\n",
    "import torch\n",
    "import time\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "uv_a_matches:  (tensor([1.]), tensor([1.]))\n",
      "matches_a:  tensor([31.])\n"
     ]
    }
   ],
   "source": [
    "def uvto1d(points, H):\n",
    "    # assert points.dim == 2\n",
    "#     print(\"points: \", points[0])\n",
    "#     print(\"H: \", H)\n",
    "    return points[0]*H + points[1]\n",
    "\n",
    "H, W = 30, 40\n",
    "uv_a_matches = (torch.tensor(np.array([1]), dtype=torch.float32), \n",
    "                torch.tensor(np.array([1]), dtype=torch.float32))\n",
    "matches_a = uv_a_matches[0]*H + uv_a_matches[1]\n",
    "matches_a = uvto1d(uv_a_matches, H)\n",
    "print(\"uv_a_matches: \", uv_a_matches)\n",
    "print(\"matches_a: \", matches_a)\n",
    "\n",
    "uv_b_matches = (torch.tensor(np.array([1]), dtype=torch.float32), \n",
    "                torch.tensor(np.array([1]), dtype=torch.float32))\n",
    "matches_b = uvto1d(uv_b_matches, H)\n",
    "\n",
    "img_b_shape = (H, W)\n",
    "img_a_shape = img_b_shape\n",
    "\n",
    "# image_a_pred\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.003054380416870117 seconds for non-matches\n",
      "torch.Size([1, 10])\n",
      "tensor(0.)\n",
      "tensor(0.)\n",
      "tensor(39.)\n",
      "tensor(28.)\n"
     ]
    }
   ],
   "source": [
    "# num_attempts = 5\n",
    "\n",
    "# img_a_index = dataset.get_random_image_index(scene)\n",
    "# img_a_rgb, img_a_depth, _, img_a_pose = dataset.get_rgbd_mask_pose(scene, img_a_index)\n",
    "\n",
    "# img_b_index = dataset.get_img_idx_with_different_pose(scene, img_a_pose, num_attempts=50)\n",
    "# img_b_rgb, img_b_depth, _, img_b_pose = dataset.get_rgbd_mask_pose(scene, img_b_index)\n",
    "\n",
    "# img_a_depth_numpy = np.asarray(img_a_depth)\n",
    "# img_b_depth_numpy = np.asarray(img_b_depth)\n",
    "\n",
    "# start = time.time()\n",
    "# uv_a, uv_b = correspondence_finder.batch_find_pixel_correspondences(img_a_depth_numpy, img_a_pose, \n",
    "#                                                                     img_b_depth_numpy, img_b_pose,\n",
    "#                                                                     num_attempts=num_attempts,\n",
    "#                                                                     device='CPU')\n",
    "\n",
    "\n",
    "start = time.time()\n",
    "# uv_b_non_matches = correspondence_finder.create_non_correspondences(uv_b, img_a_depth_numpy.shape, num_non_matches_per_match=10)\n",
    "uv_b_non_matches = correspondence_finder.create_non_correspondences(uv_b_matches, img_b_shape, num_non_matches_per_match=10, img_b_mask=None)\n",
    "print  (time.time() - start, \"seconds for non-matches\")\n",
    "if uv_b_non_matches is not None:\n",
    "    print (uv_b_non_matches[0].shape)\n",
    "\n",
    "    import torch\n",
    "    # This just checks to make sure nothing is out of bounds\n",
    "    print (torch.min(uv_b_non_matches[0]))\n",
    "    print (torch.min(uv_b_non_matches[1]))\n",
    "    print (torch.max(uv_b_non_matches[0]))\n",
    "    print (torch.max(uv_b_non_matches[1]))\n",
    "    \n",
    "#     fig, axes = correspondence_plotter.plot_correspondences_direct(img_a_rgb, img_a_depth_numpy, img_b_rgb, img_b_depth_numpy, uv_a, uv_b, show=False)\n",
    "#     uv_a_long = (torch.t(uv_a[0].repeat(3, 1)).contiguous().view(-1,1), torch.t(uv_a[1].repeat(3, 1)).contiguous().view(-1,1))\n",
    "#     uv_b_non_matches_long = (uv_b_non_matches[0].view(-1,1), uv_b_non_matches[1].view(-1,1) )\n",
    "#     correspondence_plotter.plot_correspondences_direct(img_a_rgb, img_a_depth_numpy, img_b_rgb, img_b_depth_numpy, uv_a_long, uv_b_non_matches_long, use_previous_plot=(fig,axes),\n",
    "#                                                   circ_color='r')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "uv_a_matches  (tensor([1.]), tensor([1.]))\n",
      "img_a_shape  (30, 40)\n",
      "non_matches_a:  tensor([[ 135.,  353.,   35.,  235., 1047.,  324.,  338.,  508.,  238.,   10.]])\n"
     ]
    }
   ],
   "source": [
    "print(\"uv_a_matches \", uv_a_matches)\n",
    "print(\"img_a_shape \", img_a_shape)\n",
    "uv_a_non_matches = correspondence_finder.create_non_correspondences(uv_a_matches, img_a_shape, num_non_matches_per_match=10, img_b_mask=None)\n",
    "uv_b_non_matches = correspondence_finder.create_non_correspondences(uv_b_matches, img_b_shape, num_non_matches_per_match=10, img_b_mask=None)\n",
    "\n",
    "non_matches_a = uvto1d(uv_a_non_matches, H)\n",
    "non_matches_b = uvto1d(uv_b_non_matches, H)\n",
    "\n",
    "print(\"non_matches_a: \", non_matches_a)\n",
    "\n",
    "# uv_b_non_matches # (u, v)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1.])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torch.tensor(np.array([1]), dtype=torch.float32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from utils.loss_functions.pixelwise_contrastive_loss import PixelwiseContrastiveLoss\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pltImshow(img):\n",
    "    from matplotlib import pyplot as plt\n",
    "    plt.imshow(img)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "D = 3\n",
    "# image_a = torch.tensor(np.random.rand(H, W), dtype=torch.float32)\n",
    "# image_b = torch.tensor(np.random.rand(H, W), dtype=torch.float32)\n",
    "\n",
    "image_a_pred = torch.tensor(np.random.rand(1, H*W, D), dtype=torch.float32)\n",
    "image_b_pred = torch.tensor(np.random.rand(1, H*W, D), dtype=torch.float32)\n",
    "\n",
    "# pltImshow(image_a.numpy())\n",
    "# pltImshow(image_b.numpy())\n",
    "# print(\"image_a_pred: \", ))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "match_loss:  tensor(0.0957)\n",
      "matches_a_descriptors:  torch.Size([1, 1, 1, 3])\n",
      "matches_b_descriptors:  torch.Size([1, 1, 1, 3])\n",
      "non_match_loss:  tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])\n",
      "num_hard_negatives:  0\n",
      "non_matches_a_descriptors:  torch.Size([10, 3])\n"
     ]
    }
   ],
   "source": [
    "alpha = 0.5\n",
    "\n",
    "match_loss, matches_a_descriptors, matches_b_descriptors = \\\n",
    "    PixelwiseContrastiveLoss.match_loss(image_a_pred, image_b_pred, matches_a.long(), matches_b.long())\n",
    "non_match_loss, num_hard_negatives, non_matches_a_descriptors, non_matches_b_descriptors = \\\n",
    "    PixelwiseContrastiveLoss.non_match_descriptor_loss(image_a_pred, image_b_pred, \n",
    "                                                   non_matches_a.long().squeeze(), non_matches_b.long().squeeze())\n",
    "\n",
    "\n",
    "print(\"match_loss: \", match_loss)\n",
    "print(\"matches_a_descriptors: \", matches_a_descriptors.shape)\n",
    "print(\"matches_b_descriptors: \", matches_b_descriptors.shape)\n",
    "\n",
    "print(\"non_match_loss: \", non_match_loss)\n",
    "print(\"num_hard_negatives: \", num_hard_negatives)\n",
    "print(\"non_matches_a_descriptors: \", non_matches_a_descriptors.shape)\n",
    "\n",
    "# loss, match_loss, non_match_loss = \\\n",
    "#     PixelwiseContrastiveLoss.get_triplet_loss(image_a_pred,\n",
    "#                                         image_b_pred,\n",
    "#                                         matches_a.long(),\n",
    "#                                         matches_b.long(),\n",
    "#                                         non_matches_a.long(),\n",
    "#                                         non_matches_b.long(), \n",
    "#                                         alpha = alpha)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# def scale_homography(H, shape, shift=(-1,-1)):\n",
    "#     height, width = shape[0], shape[1]\n",
    "#     trans = np.array([[2./width, 0., shift[0]], [0., 2./height, shift[1]], [0., 0., 1.]])\n",
    "#     H_tf = np.linalg.inv(trans) @ H @ trans\n",
    "#     return H_tf\n",
    "\n",
    "# def scale_homography_torch(H, shape, shift=(-1,-1), dtype=torch.float32):\n",
    "#     height, width = shape[0], shape[1]\n",
    "#     trans = torch.tensor([[2./width, 0., shift[0]], [0., 2./height, shift[1]], [0., 0., 1.]], dtype=dtype)\n",
    "#     print(\"torch.inverse(trans) \", torch.inverse(trans))\n",
    "#     print(\"H: \", H)\n",
    "#     H_tf = torch.inverse(trans) @ H @ trans\n",
    "#     return H_tf\n",
    "\n",
    "\n",
    "# # homographies = np.identity(3)[np.newaxis,:,:]\n",
    "# from utils.homographies import sample_homography_np as sample_homography\n",
    "\n",
    "# shape = np.array([2,2])\n",
    "# # homographies = sample_homography(shape, shift=-1)[np.newaxis,:,:]\n",
    "# homographies = sample_homography_batches(config, batch_size, shape=shape, tf=False)\n",
    "\n",
    "\n",
    "# height, width = 240, 320\n",
    "# image_shape = np.array([height, width])\n",
    "\n",
    "# homographies_H = np.stack([scale_homography(homographies, image_shape, shift=(-1,-1)) for H in homographies])\n",
    "# print(\"homographies_H: \", homographies_H)\n",
    "\n",
    "# homographies_H = np.stack([scale_homography_torch(torch.tensor(homographies, dtype=torch.float32), \n",
    "#                                                   image_shape, shift=(-1,-1)) for H in homographies])\n",
    "# print(\"homographies_H_torch: \", homographies_H)\n",
    "\n",
    "# print(\"homographies: \", homographies)\n",
    "\n",
    "# def plotHomography(homographies, height, width):\n",
    "#     import matplotlib.pyplot as plt\n",
    "#     from utils.utils import warp_points_np\n",
    "#     from utils.draw import drawBox\n",
    "    \n",
    "#     corner_img = np.array([(0, 0), (0, height), (width, height), (width, 0)])\n",
    "#     points = warp_points_np(corner_img, homographies)\n",
    "\n",
    "#     # plot shapes\n",
    "#     offset = np.array([height,width])\n",
    "#     img = np.zeros((height + offset[0]*2, width + offset[1]*2,3), np.uint8)\n",
    "#     img = drawBox(corner_img, img, color=(255,0,0), offset=offset)\n",
    "#     for i in range(points.shape[0]):\n",
    "#         img = drawBox(points[i,:,:], img, color=(0,255,0), offset=offset)\n",
    "#     print(\"print forward homographies\")\n",
    "#     plt.imshow(img)\n",
    "#     plt.show()\n",
    "    \n",
    "# plotHomography(homographies_H, height, width)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "config path:  configs/superpoint_coco_test.yaml\n",
      "config:  {'data': {'name': 'coco', 'dataset': 'coco', 'labels': 'magicpoint_synth20_homoAdapt100_coco/predictions', 'cache_in_memory': False, 'validation_size': 10, 'preprocessing': {'resize': [240, 320]}, 'augmentation': {'photometric': {'enable': True, 'primitives': ['random_brightness', 'random_contrast', 'additive_speckle_noise', 'additive_gaussian_noise', 'additive_shade', 'motion_blur'], 'params': {'random_brightness': {'max_abs_change': 50}, 'random_contrast': {'strength_range': [0.5, 1.5]}, 'additive_gaussian_noise': {'stddev_range': [0, 10]}, 'additive_speckle_noise': {'prob_range': [0, 0.0035]}, 'additive_shade': {'transparency_range': [-0.5, 0.5], 'kernel_size_range': [100, 150]}, 'motion_blur': {'max_kernel_size': 3}}}, 'homographic': {'enable': False}}, 'warped_pair': {'enable': True, 'params': {'translation': True, 'rotation': True, 'scaling': True, 'perspective': True, 'scaling_amplitude': 0.2, 'perspective_amplitude_x': 0.2, 'perspective_amplitude_y': 0.2, 'patch_ratio': 0.85, 'max_angle': 1.57, 'allow_artifacts': True}, 'valid_border_margin': 3}}, 'model': {'name': 'magic_point', 'batch_size': 2, 'eval_batch_size': 2, 'learning_rate': 0.0001, 'detection_threshold': 0.015, 'descriptor_dist': 7.5, 'lambda_d': 800, 'lambda_loss': 1, 'nn_thresh': 2.0, 'nms': 4}, 'retrain': True, 'reset_iter': True, 'train_iter': 200, 'validation_interval': 100, 'save_interval': 40, 'pretrained': 'logs/superpoint_coco40_1/checkpoints/superPointNet_170000_checkpoint.pth.tar'}\n"
     ]
    }
   ],
   "source": [
    "def loadConfig(filename):\n",
    "    import yaml\n",
    "    with open(filename, 'r') as f:\n",
    "        config = yaml.load(f)\n",
    "    return config\n",
    "\n",
    "filename = 'configs/superpoint_coco_test.yaml'\n",
    "# filename = 'configs/magicpoint_repeatability.yaml'\n",
    "config = loadConfig(filename)\n",
    "print(\"config path: \", filename)\n",
    "print(\"config: \", config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### sample homography"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "homographies:  [[[ 1.39820652e-02  8.78843750e-01  7.30776572e-02]\n",
      "  [-9.08425345e-01  6.99839662e-04 -1.64110523e-01]\n",
      "  [ 1.81757503e-01 -6.24586614e-10  1.00000000e+00]]]\n"
     ]
    }
   ],
   "source": [
    "# sample homography\n",
    "def sample_homography_batches(config, batch_size=1, shape=np.array([1,1]), tf=False):\n",
    "    offset = 0\n",
    "    b = 2\n",
    "\n",
    "    if tf:\n",
    "        from utils.homographies import sample_homography as sample_homography\n",
    "#         shape = np.array([b, b])\n",
    "        mat_homographies = [sample_homography(shape,\n",
    "                                          **config['data']['augmentation']['homographic']['params']) for i in range(batch_size)]\n",
    "        \n",
    "#         mat_homographies = [scale_homography(sample_homography(shape,\n",
    "#                                           **config['data']['warped_pair']['params']), shape)\n",
    "#                                             for i in range(batch_size)]\n",
    "    else:\n",
    "        from utils.homographies import sample_homography_np as sample_homography\n",
    "        mat_homographies = [sample_homography(shape, shift=-1 + offset,\n",
    "                                          **config['data']['warped_pair']['params']) for i in range(batch_size)]\n",
    "    \n",
    "    mat_homographies = np.stack(mat_homographies, axis=0)\n",
    "    return mat_homographies\n",
    "\n",
    "# homographies = sample_homography_batches(config, shape = np.array([2,2]))\n",
    "homographies = sample_homography_batches(config, batch_size=1, shape=np.array([2,2]), tf=False)\n",
    "\n",
    "print(\"homographies: \", homographies)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "homography visualization for my inplementation\n",
      "homographies:  [[[ 5.58618528e-01  1.52670420e-01 -1.02546750e+00]\n",
      "  [-3.74523367e-01  8.70181368e-01  1.24698397e+01]\n",
      "  [-1.65837289e-02  4.28585990e-10  1.33167457e+00]]]\n",
      "print forward homographies\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "print inverse homographies\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# homography visualization for my inplementation\n",
    "print(\"homography visualization for my inplementation\")\n",
    "\n",
    "from utils.utils import warp_points_np\n",
    "from numpy.linalg import inv\n",
    "import matplotlib.pyplot as plt\n",
    "from utils.draw import drawBox\n",
    "from utils.homographies import scale_homography\n",
    "\n",
    "# sample homography\n",
    "batch_size = 1\n",
    "shape = np.array([2,2])\n",
    "homographies = sample_homography_batches(config, batch_size, shape=shape, tf=False)\n",
    "homographies_original = homographies.copy()\n",
    "\n",
    "# change shapes\n",
    "height, width = 30, 40\n",
    "image_shape = np.array([height, width])\n",
    "homographies = np.stack([scale_homography(H, image_shape, shift=(-1,-1)) for H in homographies])\n",
    "print(\"homographies: \", homographies)\n",
    "\n",
    "# warp points\n",
    "# from utils.utils import warp_points_np\n",
    "# corner_img = np.array([(0, 0), (height, 0), (height, width), (0, width)])\n",
    "corner_img = np.array([(0, 0), (0, height), (width, height), (width, 0)])\n",
    "points = warp_points_np(corner_img, homographies)\n",
    "\n",
    "# plot shapes\n",
    "offset = np.array([height,width])\n",
    "img = np.zeros((height + offset[0]*2, width + offset[1]*2,3), np.uint8)\n",
    "img = drawBox(corner_img, img, color=(255,0,0), offset=offset)\n",
    "for i in range(points.shape[0]):\n",
    "    img = drawBox(points[i,:,:], img, color=(0,255,0), offset=offset)\n",
    "print(\"print forward homographies\")\n",
    "plt.imshow(img)\n",
    "plt.show()\n",
    "\n",
    "#  inv_homography\n",
    "inv_homographies = np.stack([inv(H) for H in homographies])\n",
    "\n",
    "# warp points\n",
    "# from utils.utils import warp_points_np\n",
    "# corner_img = np.array([(0, 0), (height, 0), (height, width), (0, width)])\n",
    "corner_img = np.array([(0, 0), (0, height), (width, height), (width, 0)])\n",
    "points = warp_points_np(corner_img, inv_homographies)\n",
    "\n",
    "# plot shapes\n",
    "offset = np.array([height,width])\n",
    "img = np.zeros((height + offset[0]*2, width + offset[1]*2,3), np.uint8)\n",
    "img = drawBox(corner_img, img, color=(255,0,0), offset=offset)\n",
    "for i in range(points.shape[0]):\n",
    "    img = drawBox(points[i,:,:], img, color=(0,255,0), offset=offset)\n",
    "    \n",
    "print(\"print inverse homographies\")\n",
    "plt.imshow(img)\n",
    "\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "coor_cells:  tensor([[ 0.,  0.],\n",
      "        [ 1.,  0.],\n",
      "        [ 2.,  0.],\n",
      "        ...,\n",
      "        [37., 29.],\n",
      "        [38., 29.],\n",
      "        [39., 29.]])\n",
      "coor_cells:  torch.Size([1200, 2])\n",
      "matches_a:  torch.Size([2])\n",
      "matches_b:  torch.Size([2])\n"
     ]
    }
   ],
   "source": [
    "H, W = 240, 320\n",
    "cell_size = 8\n",
    "Hc, Wc = H//cell_size, W//cell_size\n",
    "\n",
    "device = 'cpu'\n",
    "\n",
    "def get_coor_cells(Hc, Wc, cell_size, device='cpu', uv=False):\n",
    "    coor_cells = torch.stack(torch.meshgrid(torch.arange(Hc), torch.arange(Wc)), dim=2)\n",
    "    coor_cells = coor_cells.type(torch.FloatTensor).to(device)\n",
    "    coor_cells = coor_cells.view(-1, 2)\n",
    "    # change vu to uv\n",
    "    if uv:\n",
    "        coor_cells = torch.stack((coor_cells[:,1], coor_cells[:,0]), dim=1) # (y, x) to (x, y)\n",
    "\n",
    "    return coor_cells.to(device)\n",
    "coor_cells = get_coor_cells(Hc, Wc, cell_size=cell_size, device=device, uv=True)\n",
    "print(\"coor_cells: \", coor_cells)\n",
    "print(\"coor_cells: \", coor_cells.shape)\n",
    "\n",
    "from utils.utils import filter_points\n",
    "filtered_points, mask = filter_points(coor_cells, torch.tensor([Wc, Hc]), return_mask=True)\n",
    "\n",
    "\n",
    "def warp_coor_cells_with_homographies(coor_cells, homographies, uv=False, device='cpu'):\n",
    "    from utils.utils import warp_points\n",
    "    # warped_coor_cells = warp_points(coor_cells.view([-1, 2]), homographies, device)\n",
    "    # warped_coor_cells = normPts(coor_cells.view([-1, 2]), shape)\n",
    "    warped_coor_cells = coor_cells\n",
    "    if uv == False:\n",
    "        warped_coor_cells = torch.stack((warped_coor_cells[:,1], warped_coor_cells[:,0]), dim=1) # (y, x) to (x, y)\n",
    "\n",
    "    # print(\"homographies: \", homographies)\n",
    "    warped_coor_cells = warp_points(warped_coor_cells, homographies, device)\n",
    "\n",
    "    if uv == False:\n",
    "        warped_coor_cells = torch.stack((warped_coor_cells[:, :, 1], warped_coor_cells[:, :, 0]), dim=2)  # (batch, x, y) to (batch, y, x)\n",
    "\n",
    "    # shape_cell = torch.tensor([H//cell_size, W//cell_size]).type(torch.FloatTensor).to(device)\n",
    "    # warped_coor_mask = denormPts(warped_coor_cells, shape_cell)\n",
    "\n",
    "    return warped_coor_cells\n",
    "\n",
    "\n",
    "\n",
    "matches_a = uvto1d(coor_cells, Hc)\n",
    "print(\"matches_a: \", matches_a.shape)\n",
    "\n",
    "matches_b = matches_a\n",
    "print(\"matches_b: \", matches_b.shape)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "match_loss:  tensor(0.1613)\n",
      "matches_a_descriptors:  torch.Size([1, 2, 3])\n",
      "matches_b_descriptors:  torch.Size([1, 2, 3])\n"
     ]
    }
   ],
   "source": [
    "match_loss, matches_a_descriptors, matches_b_descriptors = \\\n",
    "    PixelwiseContrastiveLoss.match_loss(image_a_pred, image_b_pred, matches_a.long(), matches_b.long())\n",
    "print(\"match_loss: \", match_loss)\n",
    "print(\"matches_a_descriptors: \", matches_a_descriptors.shape)\n",
    "print(\"matches_b_descriptors: \", matches_b_descriptors.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_non_matches(uv_a, uv_b_non_matches, multiplier):\n",
    "    \"\"\"\n",
    "    Simple wrapper for repeated code\n",
    "    :param uv_a:\n",
    "    :type uv_a:\n",
    "    :param uv_b_non_matches:\n",
    "    :type uv_b_non_matches:\n",
    "    :param multiplier:\n",
    "    :type multiplier:\n",
    "    :return:\n",
    "    :rtype:\n",
    "    \"\"\"\n",
    "    uv_a_long = (torch.t(uv_a[0].repeat(multiplier, 1)).contiguous().view(-1, 1),\n",
    "                 torch.t(uv_a[1].repeat(multiplier, 1)).contiguous().view(-1, 1))\n",
    "\n",
    "    uv_b_non_matches_long = (uv_b_non_matches[0].view(-1, 1), uv_b_non_matches[1].view(-1, 1))\n",
    "\n",
    "    return uv_a_long, uv_b_non_matches_long"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## update sparse loss function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "# def descriptor_loss_sparse(descriptors, descriptors_warped, homographies, mask_valid=None,\n",
    "#                            cell_size=8, device='cpu', descriptor_dist=4, lamda_d=250,\n",
    "#                            num_matching_attempts=1000, num_masked_non_matches_per_match=10, **config):\n",
    "#     \"\"\"\n",
    "#     consider batches of descriptors\n",
    "#     :param descriptors:\n",
    "#         Output from descriptor head\n",
    "#         tensor [descriptors, Hc, Wc]\n",
    "#     :param descriptors_warped:\n",
    "#         Output from descriptor head of warped image\n",
    "#         tensor [descriptors, Hc, Wc]\n",
    "#     \"\"\"\n",
    "\n",
    "#     def uv_to_tuple(uv):\n",
    "#         return (uv[:, 0], uv[:, 1])\n",
    "\n",
    "#     def tuple_to_uv(uv_tuple):\n",
    "#         return torch.stack([uv_tuple[0], uv_tuple[1]], dim=-1).squeeze()\n",
    "\n",
    "#     def tuple_to_1d(uv_tuple, H):\n",
    "#         return uv_tuple[0] * H + uv_tuple[1]\n",
    "\n",
    "#     def uv_to_1d(points, H):\n",
    "#         # assert points.dim == 2\n",
    "#     #     print(\"points: \", points[0])\n",
    "#     #     print(\"H: \", H)\n",
    "#         return points[...,0]*H + points[...,1]\n",
    "\n",
    "#     from utils.utils import filter_points\n",
    "#     from utils.utils import crop_or_pad_choice\n",
    "\n",
    "#     Hc, Wc = descriptors.shape[1], descriptors.shape[2]\n",
    "#     img_shape = (Hc, Wc)\n",
    "    \n",
    "#     image_a_pred = descriptors.view(1, -1, Hc*Wc).transpose(1,2)  # torch [batch_size, H*W, D]\n",
    "#     print(\"image_a_pred: \", image_a_pred.shape)\n",
    "#     image_b_pred = descriptors_warped.view(1, -1, Hc*Wc).transpose(1,2)  # torch [batch_size, H*W, D]\n",
    "#     print(\"image_a_pred: \", image_a_pred.shape)\n",
    "\n",
    "#     # matches\n",
    "#     ## sample points (implement sampling later)\n",
    "#     ## warp points\n",
    "#         # coor_cell\n",
    "#     uv_a = get_coor_cells(Hc, Wc, cell_size, uv=True)\n",
    "#     print(\"uv_a: \", uv_a.shape)\n",
    "#         # warp coor_cell\n",
    "# #     homographies_H = \n",
    "# #     homographies_H = np.stack([scale_homography_torch(homographies, image_shape, shift=(-1,-1)) for H in homographies])\n",
    "#     homographies_H = scale_homography_torch(homographies, image_shape, shift=(-1,-1))\n",
    "#     print(\"homographies_H: \", homographies_H)\n",
    "    \n",
    "#     uv_b_matches = warp_coor_cells_with_homographies(uv_a, homographies_H, uv=True)\n",
    "#     uv_b_matches = uv_b_matches.squeeze(0)\n",
    "#     print(\"uv_b_matches: \", uv_b_matches.shape)\n",
    "#     print(\"uv_b_matches all: \", uv_b_matches)\n",
    "\n",
    "#     # filtering!!!\n",
    "#     # choice = crop_or_pad_choice(x_all.shape[0], self.sift_num, shuffle=True)\n",
    "\n",
    "#     uv_b_matches, mask = filter_points(uv_b_matches, torch.tensor([Wc, Hc]), return_mask=True)\n",
    "\n",
    "#     # batch the uv_a\n",
    "#     # uv_a = uv_a.unsqueeze(0).repeat(batch_size, 1, 1)\n",
    "#     uv_a = uv_a[mask]\n",
    "\n",
    "#     # unsqueeze to batch\n",
    "#     # if batch_size == 1:\n",
    "#     #     uv_a = uv_a.unsqueeze(0)\n",
    "#     #     uv_b_matches = uv_b_matches.unsqueeze(0)\n",
    "\n",
    "#     # crop to the same length\n",
    "#     print(\"shuffle = false\")\n",
    "#     choice = crop_or_pad_choice(uv_b_matches.shape[0], num_matching_attempts, shuffle=False)\n",
    "#     choice = torch.tensor(choice)\n",
    "#     uv_a =         uv_a[choice]\n",
    "#     uv_b_matches = uv_b_matches[choice]\n",
    "\n",
    "#     matches_a = uv_to_1d(uv_a, Hc)\n",
    "#     print(\"matches_a: \", matches_a.shape)\n",
    "#     matches_b = uv_to_1d(uv_b_matches, Hc)\n",
    "#     print(\"matches_b: \", matches_b.shape)\n",
    "# #     print(\"matches_b max: \", matches_b.max())\n",
    "\n",
    "#     # matches_a = matches_a.squeeze()\n",
    "#     # matches_b = matches_b.squeeze()\n",
    "#     # matches_a = matches_a[0, ...]\n",
    "#     # matches_b = matches_b[0, ...]\n",
    "\n",
    "\n",
    "#     ## select from descriptor\n",
    "#     # def get_match_loss\n",
    "#     ## calculate matches loss\n",
    "#     def get_match_loss(image_a_pred, image_b_pred, matches_a, matches_b):\n",
    "#         match_loss, matches_a_descriptors, matches_b_descriptors = \\\n",
    "#             PixelwiseContrastiveLoss.match_loss(image_a_pred, image_b_pred, matches_a.long(), matches_b.long())\n",
    "#         print(\"matches_a_descriptors: \", matches_a_descriptors.shape)\n",
    "#         return match_loss\n",
    "\n",
    "#     # batch_match_loss = torch.stack([get_match_loss(image_a_pred[i], image_b_pred[i], matches_a[i], matches_b[i])\n",
    "#     #                                          for i in range(batch_size)])\n",
    "\n",
    "#     match_loss = get_match_loss(image_a_pred, image_b_pred, matches_a, matches_b)\n",
    "\n",
    "#     # non matches\n",
    "\n",
    "#     def get_non_matches_corr(img_b_shape, uv_a, uv_b_matches, num_masked_non_matches_per_match=10):\n",
    "#         ## sample non matches\n",
    "#         uv_b_matches = uv_b_matches.squeeze()\n",
    "#         uv_b_matches_tuple = uv_to_tuple(uv_b_matches)\n",
    "#     #     print(\"uv_b_matches \", uv_b_matches.shape)\n",
    "# #         print(\"img_b_shape \", img_b_shape)\n",
    "#         uv_b_non_matches_tuple = correspondence_finder.create_non_correspondences(uv_b_matches_tuple,\n",
    "#                         img_b_shape, num_non_matches_per_match=num_masked_non_matches_per_match, img_b_mask=None)\n",
    "\n",
    "#         ## create_non_correspondences\n",
    "# #         print(\"uv_a: \", uv_to_tuple(uv_a))\n",
    "# #         print(\"uv_b_non_matches: \", uv_b_non_matches)\n",
    "#     #     print(\"uv_b_non_matches: \", tensorUv2tuple(uv_b_non_matches))\n",
    "#         uv_a_tuple, uv_b_non_matches_tuple = \\\n",
    "#             create_non_matches(uv_to_tuple(uv_a), uv_b_non_matches_tuple, num_masked_non_matches_per_match)\n",
    "#         return uv_a_tuple, uv_b_non_matches_tuple\n",
    "\n",
    "\n",
    "#     def get_non_match_loss(image_a_pred, image_b_pred, non_matches_a, non_matches_b):\n",
    "#         ## non matches loss\n",
    "#         non_match_loss, num_hard_negatives, non_matches_a_descriptors, non_matches_b_descriptors = \\\n",
    "#                         PixelwiseContrastiveLoss.non_match_descriptor_loss(image_a_pred, image_b_pred,\n",
    "#                                                    non_matches_a.long().squeeze(), non_matches_b.long().squeeze())\n",
    "#         return non_match_loss\n",
    "\n",
    "#     # get non matches correspondence\n",
    "#     uv_a_tuple, uv_b_non_matches_tuple = get_non_matches_corr(img_shape,\n",
    "#                                      uv_a, uv_b_matches,\n",
    "#                                      num_masked_non_matches_per_match=num_masked_non_matches_per_match)\n",
    "\n",
    "#     non_matches_a = tuple_to_1d(uv_a_tuple, Hc)\n",
    "#     non_matches_b = tuple_to_1d(uv_b_non_matches_tuple, Hc)\n",
    "\n",
    "# #     print(\"non_matches_a: \", non_matches_a)\n",
    "# #     print(\"non_matches_b: \", non_matches_b)\n",
    "\n",
    "#     non_match_loss = get_non_match_loss(image_a_pred, image_b_pred, non_matches_a, non_matches_a)\n",
    "#     non_match_loss = non_match_loss.mean()\n",
    "\n",
    "#     # batch_non_match_loss = []\n",
    "#     # for i in range(batch_size):\n",
    "#     #     # get non matches correspondence\n",
    "#     #     uv_a_tuple, uv_b_non_matches_tuple = get_non_matches_corr(img_b_shape,\n",
    "#     #                                      batch_uv_a[i], uv_b_non_matches[i],\n",
    "#     #                                      num_masked_non_matches_per_match=num_masked_non_matches_per_match)\n",
    "#     #\n",
    "#     #     non_matches_a = tuple_to_1d(uv_a_tuple, Hc)\n",
    "#     #     non_matches_b = tuple_to_1d(uv_b_non_matches_tuple, Hc)\n",
    "#     #\n",
    "#     #     print(\"non_matches_a: \", non_matches_a)\n",
    "#     #     print(\"non_matches_b: \", non_matches_b)\n",
    "#     #\n",
    "#     #     non_match_loss = torch.stack([get_non_match_loss(image_a_pred[i], image_b_pred[i],\n",
    "#     #                                          non_matches_a[i], non_matches_a[i])\n",
    "#     #                                          for i in range(batch_size)])\n",
    "#     #     batch_non_match_loss.append(non_match_loss)\n",
    "\n",
    "#     # batch_match_loss = batch_match_loss.mean()\n",
    "#     # batch_non_match_loss = torch.stack(batch_non_match_loss).mean()\n",
    "\n",
    "#     loss = lamda_d*match_loss + non_match_loss\n",
    "#     print(\"plot matches and non matches\")\n",
    "#     return uv_a, uv_b_matches, tuple_to_uv(uv_a_tuple), tuple_to_uv(uv_b_non_matches_tuple)\n",
    "# #     return uv_a, uv_b_matches\n",
    "# #     return loss\n",
    "#     pass\n",
    "\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## New version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "def descriptor_loss_sparse(descriptors, descriptors_warped, homographies, mask_valid=None,\n",
    "                           cell_size=8, device='cpu', descriptor_dist=4, lamda_d=250,\n",
    "                           num_matching_attempts=1000, num_masked_non_matches_per_match=10, \n",
    "                           dist='cos', **config):\n",
    "    \"\"\"\n",
    "    consider batches of descriptors\n",
    "    :param descriptors:\n",
    "        Output from descriptor head\n",
    "        tensor [descriptors, Hc, Wc]\n",
    "    :param descriptors_warped:\n",
    "        Output from descriptor head of warped image\n",
    "        tensor [descriptors, Hc, Wc]\n",
    "    \"\"\"\n",
    "\n",
    "    def uv_to_tuple(uv):\n",
    "        return (uv[:, 0], uv[:, 1])\n",
    "\n",
    "    def tuple_to_uv(uv_tuple):\n",
    "        return torch.stack([uv_tuple[0], uv_tuple[1]])\n",
    "\n",
    "    def tuple_to_1d(uv_tuple, H):\n",
    "        return uv_tuple[0] * H + uv_tuple[1]\n",
    "\n",
    "    def uv_to_1d(points, H):\n",
    "        # assert points.dim == 2\n",
    "        #     print(\"points: \", points[0])\n",
    "        #     print(\"H: \", H)\n",
    "        return points[..., 0] * H + points[..., 1]\n",
    "\n",
    "    ## calculate matches loss\n",
    "    def get_match_loss(image_a_pred, image_b_pred, matches_a, matches_b, dist='cos'):\n",
    "        match_loss, matches_a_descriptors, matches_b_descriptors = \\\n",
    "            PixelwiseContrastiveLoss.match_loss(image_a_pred, image_b_pred, \n",
    "                matches_a.long(), matches_b.long(), dist=dist)\n",
    "        return match_loss\n",
    "\n",
    "    def get_non_matches_corr(img_b_shape, uv_a, uv_b_matches, num_masked_non_matches_per_match=10, device='cpu'):\n",
    "        ## sample non matches\n",
    "        uv_b_matches = uv_b_matches.squeeze()\n",
    "        uv_b_matches_tuple = uv_to_tuple(uv_b_matches)\n",
    "        uv_b_non_matches_tuple = correspondence_finder.create_non_correspondences(uv_b_matches_tuple,\n",
    "                                        img_b_shape, num_non_matches_per_match=num_masked_non_matches_per_match,\n",
    "                                        img_b_mask=None)\n",
    "\n",
    "        ## create_non_correspondences\n",
    "        #     print(\"img_b_shape \", img_b_shape)\n",
    "        #     print(\"uv_b_matches \", uv_b_matches.shape)\n",
    "        # print(\"uv_a: \", uv_to_tuple(uv_a))\n",
    "        # print(\"uv_b_non_matches: \", uv_b_non_matches)\n",
    "        #     print(\"uv_b_non_matches: \", tensorUv2tuple(uv_b_non_matches))\n",
    "        uv_a_tuple, uv_b_non_matches_tuple = \\\n",
    "            create_non_matches(uv_to_tuple(uv_a), uv_b_non_matches_tuple, num_masked_non_matches_per_match)\n",
    "        return uv_a_tuple, uv_b_non_matches_tuple\n",
    "\n",
    "    def get_non_match_loss(image_a_pred, image_b_pred, non_matches_a, non_matches_b, dist='cos'):\n",
    "        ## non matches loss\n",
    "        non_match_loss, num_hard_negatives, non_matches_a_descriptors, non_matches_b_descriptors = \\\n",
    "            PixelwiseContrastiveLoss.non_match_descriptor_loss(image_a_pred, image_b_pred,\n",
    "                                                               non_matches_a.long().squeeze(),\n",
    "                                                               non_matches_b.long().squeeze(),\n",
    "                                                               M=0.2, invert=True, dist=dist)\n",
    "        non_match_loss = non_match_loss.sum()/(num_hard_negatives + 1)\n",
    "        return non_match_loss\n",
    "\n",
    "    from utils.utils import filter_points\n",
    "    from utils.utils import crop_or_pad_choice\n",
    "\n",
    "    # ##### print configs\n",
    "    # print(\"num_masked_non_matches_per_match: \", num_masked_non_matches_per_match)\n",
    "    # print(\"num_matching_attempts: \", num_matching_attempts)\n",
    "    # dist = 'cos'\n",
    "\n",
    "    Hc, Wc = descriptors.shape[1], descriptors.shape[2]\n",
    "    img_shape = (Hc, Wc)\n",
    "    # print(\"img_shape: \", img_shape)\n",
    "    # img_shape_cpu = (Hc.to('cpu'), Wc.to('cpu'))\n",
    "\n",
    "    image_a_pred = descriptors.view(1, -1, Hc * Wc).transpose(1, 2)  # torch [batch_size, H*W, D]\n",
    "    # print(\"image_a_pred: \", image_a_pred.shape)\n",
    "    image_b_pred = descriptors_warped.view(1, -1, Hc * Wc).transpose(1, 2)  # torch [batch_size, H*W, D]\n",
    "\n",
    "    # matches\n",
    "    uv_a = get_coor_cells(Hc, Wc, cell_size, uv=True, device='cpu')\n",
    "    # print(\"uv_a: \", uv_a[0])\n",
    "\n",
    "    homographies_H = scale_homography_torch(homographies, img_shape, shift=(-1, -1))\n",
    "\n",
    "    # print(\"experiment inverse homographies\")\n",
    "    # homographies_H = torch.stack([torch.inverse(H) for H in homographies_H])\n",
    "    # print(\"homographies_H: \", homographies_H.shape)\n",
    "    # homographies_H = torch.inverse(homographies_H)\n",
    "\n",
    "\n",
    "    uv_b_matches = warp_coor_cells_with_homographies(uv_a, homographies_H.to('cpu'), uv=True, device='cpu')\n",
    "    # \n",
    "    # print(\"uv_b_matches before round: \", uv_b_matches[0])\n",
    "\n",
    "    uv_b_matches.round_() \n",
    "    # print(\"uv_b_matches after round: \", uv_b_matches[0])\n",
    "    uv_b_matches = uv_b_matches.squeeze(0)\n",
    "\n",
    "\n",
    "    # filtering out of range points\n",
    "    # choice = crop_or_pad_choice(x_all.shape[0], self.sift_num, shuffle=True)\n",
    "\n",
    "    uv_b_matches, mask = filter_points(uv_b_matches, torch.tensor([Wc, Hc]).to(device='cpu'), return_mask=True)\n",
    "    print (\"pos mask sum: \", mask.sum())\n",
    "    uv_a = uv_a[mask]\n",
    "\n",
    "    # crop to the same length\n",
    "    choice = crop_or_pad_choice(uv_b_matches.shape[0], num_matching_attempts, shuffle=True)\n",
    "    choice = torch.tensor(choice)\n",
    "    uv_a = uv_a[choice]\n",
    "    uv_b_matches = uv_b_matches[choice]\n",
    "\n",
    "    matches_a = uv_to_1d(uv_a, Hc)\n",
    "    matches_b = uv_to_1d(uv_b_matches, Hc)\n",
    "\n",
    "    # print(\"matches_a: \", matches_a.shape)\n",
    "    # print(\"matches_b: \", matches_b.shape)\n",
    "    # print(\"matches_b max: \", matches_b.max())\n",
    "\n",
    "    match_loss = get_match_loss(image_a_pred, image_b_pred, matches_a.to(device), matches_b.to(device), dist=dist)\n",
    "\n",
    "    # non matches\n",
    "\n",
    "    # get non matches correspondence\n",
    "    uv_a_tuple, uv_b_non_matches_tuple = get_non_matches_corr(img_shape,\n",
    "                                            uv_a, uv_b_matches,\n",
    "                                            num_masked_non_matches_per_match=num_masked_non_matches_per_match)\n",
    "\n",
    "    non_matches_a = tuple_to_1d(uv_a_tuple, Hc)\n",
    "    non_matches_b = tuple_to_1d(uv_b_non_matches_tuple, Hc)\n",
    "\n",
    "    # print(\"non_matches_a: \", non_matches_a)\n",
    "    # print(\"non_matches_b: \", non_matches_b)\n",
    "\n",
    "    non_match_loss = get_non_match_loss(image_a_pred, image_b_pred, non_matches_a.to(device),\n",
    "                                        non_matches_b.to(device), dist=dist)\n",
    "    # non_match_loss = non_match_loss.mean()\n",
    "\n",
    "    loss = lamda_d * match_loss + non_match_loss\n",
    "    return uv_a, uv_b_matches, tuple_to_uv(uv_a_tuple), tuple_to_uv(uv_b_non_matches_tuple)\n",
    "    \n",
    "#     return loss, lamda_d * match_loss, non_match_loss\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "homographies_original:  [[[ 8.90293106e-01  1.14502809e-01 -3.78152032e-01]\n",
      "  [-1.67689912e-01  8.70181362e-01  2.02139527e-01]\n",
      "  [-3.31674577e-01  6.42878985e-09  1.00000000e+00]]]\n",
      "descriptors:  torch.Size([1, 3, 30, 40])\n",
      "pos mask sum:  tensor(1130)\n",
      "descriptor_loss:  (tensor([[13.,  7.],\n",
      "        [32., 20.],\n",
      "        [33., 14.],\n",
      "        ...,\n",
      "        [ 2., 17.],\n",
      "        [18., 28.],\n",
      "        [13., 16.]]), tensor([[ 7., 12.],\n",
      "        [25., 22.],\n",
      "        [25., 16.],\n",
      "        ...,\n",
      "        [ 2., 20.],\n",
      "        [13., 29.],\n",
      "        [ 8., 19.]]), tensor([[[13.],\n",
      "         [13.],\n",
      "         [13.],\n",
      "         ...,\n",
      "         [13.],\n",
      "         [13.],\n",
      "         [13.]],\n",
      "\n",
      "        [[ 7.],\n",
      "         [ 7.],\n",
      "         [ 7.],\n",
      "         ...,\n",
      "         [16.],\n",
      "         [16.],\n",
      "         [16.]]]), tensor([[[26.],\n",
      "         [35.],\n",
      "         [12.],\n",
      "         ...,\n",
      "         [23.],\n",
      "         [ 0.],\n",
      "         [11.]],\n",
      "\n",
      "        [[26.],\n",
      "         [17.],\n",
      "         [18.],\n",
      "         ...,\n",
      "         [16.],\n",
      "         [27.],\n",
      "         [21.]]]))\n",
      "pos mask sum:  tensor(1130)\n",
      "descriptor_loss test same input:  (tensor([[15., 13.],\n",
      "        [ 5., 18.],\n",
      "        [27., 24.],\n",
      "        ...,\n",
      "        [17.,  7.],\n",
      "        [35., 20.],\n",
      "        [31., 11.]]), tensor([[ 9., 17.],\n",
      "        [ 4., 21.],\n",
      "        [20., 26.],\n",
      "        ...,\n",
      "        [ 9., 12.],\n",
      "        [29., 22.],\n",
      "        [22., 13.]]), tensor([[[15.],\n",
      "         [15.],\n",
      "         [15.],\n",
      "         ...,\n",
      "         [31.],\n",
      "         [31.],\n",
      "         [31.]],\n",
      "\n",
      "        [[13.],\n",
      "         [13.],\n",
      "         [13.],\n",
      "         ...,\n",
      "         [11.],\n",
      "         [11.],\n",
      "         [11.]]]), tensor([[[38.],\n",
      "         [ 7.],\n",
      "         [27.],\n",
      "         ...,\n",
      "         [17.],\n",
      "         [ 9.],\n",
      "         [ 3.]],\n",
      "\n",
      "        [[15.],\n",
      "         [11.],\n",
      "         [12.],\n",
      "         ...,\n",
      "         [12.],\n",
      "         [ 1.],\n",
      "         [28.]]]))\n"
     ]
    }
   ],
   "source": [
    "print(\"homographies_original: \", homographies_original)\n",
    "\n",
    "descriptors = torch.tensor(np.random.rand(1, D, Hc, Wc), dtype=torch.float32)\n",
    "print(\"descriptors: \", descriptors.shape)\n",
    "descriptors_warped = torch.tensor(np.random.rand(1, D, Hc, Wc), dtype=torch.float32)\n",
    "descriptor_loss = descriptor_loss_sparse(descriptors[0], descriptors_warped[0], torch.tensor(homographies_original[0], dtype=torch.float32))\n",
    "print(\"descriptor_loss: \", descriptor_loss)\n",
    "descriptor_loss = descriptor_loss_sparse(descriptors[0], descriptors[0], torch.tensor(homographies_original[0], dtype=torch.float32))\n",
    "print(\"descriptor_loss test same input: \", descriptor_loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## visualize one pixel's matching and non matching"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pos mask sum:  tensor(1130)\n"
     ]
    }
   ],
   "source": [
    "from utils.draw import draw_matches\n",
    "from utils.homographies import scale_homography_torch\n",
    "\n",
    "uv_a, uv_b_matches, uv_a_non_matches, uv_b_non_matches  = descriptor_loss_sparse(descriptors[0], \n",
    "            descriptors_warped[0], torch.tensor(homographies_original[0], dtype=torch.float32))\n",
    "rgb1 = descriptors[0,0,:,:]\n",
    "rgb2 = descriptors_warped[0,0,:,:]\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "uv_a:  torch.Size([1000, 2])\n",
      "match_pairs:  torch.Size([1000, 4])\n"
     ]
    }
   ],
   "source": [
    "print(\"uv_a: \", uv_a.shape)\n",
    "match_pairs = torch.cat((uv_a, uv_b_matches), dim=1)\n",
    "print(\"match_pairs: \", match_pairs.shape)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "#Matches = 500\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "samples_s = 500\n",
    "samples_e = 1000\n",
    "draw_matches(rgb1.numpy(), rgb2.numpy(), match_pairs[samples_s:samples_e].numpy(), filename='matches.png', show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "uv_a_non_matches:  torch.Size([10000, 2])\n",
      "match_pairs:  torch.Size([10000, 4])\n"
     ]
    }
   ],
   "source": [
    "print(\"uv_a_non_matches: \", uv_a_non_matches.shape)\n",
    "match_pairs = torch.cat((uv_a_non_matches, uv_b_non_matches), dim=1)\n",
    "print(\"match_pairs: \", match_pairs.shape)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = 100\n",
    "draw_matches(rgb1.numpy(), rgb2.numpy(), match_pairs[:samples].numpy(), filename='matches.png', show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"homographies: \", homographies.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "uv_a_masked_long, uv_b_masked_non_matches_long = \\\n",
    "        self.create_non_matches(matches_1, matches_2_masked_non_matches, self.num_masked_non_matches_per_match)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "matches_a.long().type()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_loss_on_dataset(dcn, data_loader, loss_config, num_iterations=500,):\n",
    "    \"\"\"\n",
    "\n",
    "    Computes the loss for the given number of iterations\n",
    "\n",
    "    :param dcn:\n",
    "    :type dcn:\n",
    "    :param data_loader:\n",
    "    :type data_loader:\n",
    "    :param num_iterations:\n",
    "    :type num_iterations:\n",
    "    :return:\n",
    "    :rtype:\n",
    "    \"\"\"\n",
    "    dcn.eval()\n",
    "\n",
    "    # loss_vec = np.zeros(num_iterations)\n",
    "    loss_vec = []\n",
    "    match_loss_vec = []\n",
    "    non_match_loss_vec = []\n",
    "    counter = 0\n",
    "    pixelwise_contrastive_loss = PixelwiseContrastiveLoss(dcn.image_shape, config=loss_config)\n",
    "\n",
    "    batch_size = 1\n",
    "\n",
    "    for i, data in enumerate(data_loader, 0):\n",
    "\n",
    "        # get the inputs\n",
    "        data_type, img_a, img_b, matches_a, matches_b, non_matches_a, non_matches_b, metadata = data\n",
    "        data_type = data_type[0]\n",
    "\n",
    "        if len(matches_a[0]) == 0:\n",
    "            print (\"didn't have any matches, continuing\")\n",
    "            continue\n",
    "\n",
    "        img_a = Variable(img_a.cuda(), requires_grad=False)\n",
    "        img_b = Variable(img_b.cuda(), requires_grad=False)\n",
    "\n",
    "        if data_type == \"matches\":\n",
    "            matches_a = Variable(matches_a.cuda().squeeze(0), requires_grad=False)\n",
    "            matches_b = Variable(matches_b.cuda().squeeze(0), requires_grad=False)\n",
    "            non_matches_a = Variable(non_matches_a.cuda().squeeze(0), requires_grad=False)\n",
    "            non_matches_b = Variable(non_matches_b.cuda().squeeze(0), requires_grad=False)\n",
    "\n",
    "        # run both images through the network\n",
    "        image_a_pred = dcn.forward(img_a)\n",
    "        image_a_pred = dcn.process_network_output(image_a_pred, batch_size)\n",
    "\n",
    "        image_b_pred = dcn.forward(img_b)\n",
    "        image_b_pred = dcn.process_network_output(image_b_pred, batch_size)\n",
    "\n",
    "        # get loss\n",
    "        if data_type == \"matches\":\n",
    "            loss, match_loss, non_match_loss = \\\n",
    "                pixelwise_contrastive_loss.get_loss(image_a_pred,\n",
    "                                                    image_b_pred,\n",
    "                                                    matches_a,\n",
    "                                                    matches_b,\n",
    "                                                    non_matches_a,\n",
    "                                                    non_matches_b)\n",
    "\n",
    "\n",
    "\n",
    "            loss_vec.append(loss.data[0])\n",
    "            non_match_loss_vec.append(non_match_loss.data[0])\n",
    "            match_loss_vec.append(match_loss.data[0])\n",
    "\n",
    "\n",
    "        if i > num_iterations:\n",
    "            break\n",
    "\n",
    "    loss_vec = np.array(loss_vec)\n",
    "    match_loss_vec = np.array(match_loss_vec)\n",
    "    non_match_loss_vec = np.array(non_match_loss_vec)\n",
    "\n",
    "    loss = np.average(loss_vec)\n",
    "    match_loss = np.average(match_loss_vec)\n",
    "    non_match_loss = np.average(non_match_loss_vec)\n",
    "\n",
    "    return loss, match_loss, non_match_loss\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py36_pytorch",
   "language": "python",
   "name": "py36_pytorch"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
